package com.ruoyi.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.domain.dto.*;
import com.ruoyi.system.domain.vo.MetaVo;
import com.ruoyi.system.domain.vo.RouterVo;
import com.ruoyi.system.filter.AuthDataFilter;
import com.ruoyi.system.service.ISysMenuService;
import lombok.RequiredArgsConstructor;
import org.babyfish.jimmer.sql.JSqlClient;
import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 菜单 业务层处理
 *
 * @author ruoyi
 */

@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl implements ISysMenuService {
    public static final String PREMISSION_STRING = "perms[\"{0}\"]";

    private final JSqlClient sqlClient;

    /**
     * 根据用户查询系统菜单列表
     *
     * @param userId 用户ID
     * @return 菜单列表
     */

    @Override
    public List<SysMenuView> selectMenuList(Long userId) {
        return selectMenuList(new SysMenuListInput(), userId);
    }


    /**
     * 查询系统菜单列表
     *
     * @param menu 菜单信息
     * @return 菜单列表
     */
    @Override
    public List<SysMenuView> selectMenuList(SysMenuListInput menu, Long userId) {
        List<SysMenu> menuList;
        SysMenuTable table = SysMenuTable.$;
        if (SecurityUtils.isAdmin(userId)) {
            menuList = sqlClient.filters(it-> it.disableByTypes(AuthDataFilter.class))
                    .createQuery(table)
                    .whereIf(StrUtil.isNotBlank(menu.getMenuName()), () -> table.menuName().ilike(menu.getMenuName()))
                    .whereIf(StrUtil.isNotBlank(menu.getStatus()), () -> table.status().eq(menu.getStatus()))
                    .orderBy(table.parentId(), table.orderNum())
                    .select(table.fetch(
                            SysMenuFetcher.$.allScalarFields()
                                    .parentId())
                    )
                    .execute();

        } else {
            menuList = sqlClient.createQuery(table)
                    .whereIf(StrUtil.isNotBlank(menu.getMenuName()), () -> table.menuName().ilike(menu.getMenuName()))
                    .whereIf(StrUtil.isNotBlank(menu.getStatus()), () -> table.status().eq(menu.getStatus()))
                    .where(table.asTableEx().roles().users().userId().eq(userId))
                    .orderBy(table.parentId(), table.orderNum())
                    .select(table.fetch(
                            SysMenuFetcher.$.allScalarFields()
                                    .parentId())).execute();
        }
        return menuList.stream().map(SysMenuView::new).collect(Collectors.toList());
    }


    /**
     * 根据用户ID查询权限
     *
     * @param userId 用户ID
     * @return 权限列表
     */

    @Override
    public Set<String> selectMenuPermsByUserId(Long userId) {
        SysMenuTable table = SysMenuTable.$;
        List<String> perms = sqlClient.filters(it-> it.disableByTypes(AuthDataFilter.class))
                .createQuery(table).where(
                table.asTableEx().roles().users().userId().eq(userId)
        ).select(table.perms()).execute();
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    /**
     * 根据角色ID查询权限
     *
     * @param roleId 角色ID
     * @return 权限列表
     */

    @Override
    public Set<String> selectMenuPermsByRoleId(Long roleId) {
        SysMenuTable table = SysMenuTable.$;
        List<String> perms = sqlClient.filters(it-> it.disableByTypes(AuthDataFilter.class))
                .createQuery(table).where(
                table.asTableEx().roles().roleId().eq(roleId)
        ).select(table.perms()).execute();
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    /**
     * 根据用户ID查询菜单
     *
     * @param userId 用户名称
     * @return 菜单列表
     */
    @Override
    public List<SysMenu> selectMenuTreeByUserId(Long userId) {
        SysMenuTable table = SysMenuTable.$;
        return sqlClient.filters(it-> it.disableByTypes(AuthDataFilter.class))
                .createQuery(table)
                .where(
                        table.parentId().eq(0L),
                        table.asTableEx().roles().users().userId().eqIf(!SecurityUtils.isAdmin(userId), userId)
                )
                .orderBy(table.parentId(), table.orderNum())
                .select(
                        table.fetch(
                                SysMenuFetcher.$
                                        .allScalarFields()
                                        .parentId()
                                        .recursiveChildren(
                                                it -> it.filter(args -> {
                                                    SysMenuTable mt = args.getTable();
                                                    args.where(mt.asTableEx().roles().users().userId().eqIf(!SecurityUtils.isAdmin(userId), userId));
                                                })
                                        )
                        )
                ).execute();
    }

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId 角色ID
     * @return 选中菜单列表
     */
    @Override
    public List<Long> selectMenuListByRoleId(Long roleId) {
        SysRole role = sqlClient.filters(it-> it.disableByTypes(AuthDataFilter.class))
                .findById(
                SysRoleFetcher.$.allScalarFields()
                        .menus(SysMenuFetcher.$.parentId())
                , roleId);
        List<Long> menuIds = role.menus().stream().map(SysMenu::parentId).collect(Collectors.toList());
        SysMenuTable table = SysMenuTable.$;
        return sqlClient.createQuery(table).
                where(table.asTableEx().roles().roleId().eq(roleId),
                        table.menuId().notInIf(role.menuCheckStrictly(), menuIds))
                .select(table.menuId()).execute();
    }


    /**
     * 构建前端路由所需要的菜单
     *
     * @param menus 菜单列表
     * @return 路由列表
     */

    @Override
    public List<RouterVo> buildMenus(List<SysMenu> menus) {
        List<RouterVo> routers = new LinkedList<>();
        for (SysMenu menu : menus) {
            RouterVo router = new RouterVo();
            router.setHidden("1".equals(menu.visible()));
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setQuery(menu.query());
            router.setMeta(new MetaVo(menu.menuName(), menu.icon(), StringUtils.equals("1", menu.cacheStatus()), menu.path()));
            List<SysMenu> cMenus = menu.children();
            if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.menuType())) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (isMenuFrame(menu)) {
                router.setMeta(null);
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                children.setPath(menu.path());
                children.setComponent(menu.component());
                children.setName(getRouteName(menu.routeName(), menu.path()));
                children.setMeta(new MetaVo(menu.menuName(), menu.icon(), StringUtils.equals("1", menu.cacheStatus()), menu.path()));
                children.setQuery(menu.query());
                childrenList.add(children);
                router.setChildren(childrenList);
            } else if (menu.parentId() == 0 && isInnerLink(menu)) {
                router.setMeta(new MetaVo(menu.menuName(), menu.icon()));
                router.setPath("/");
                List<RouterVo> childrenList = new ArrayList<>();
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.path());
                children.setPath(routerPath);
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(getRouteName(menu.routeName(), routerPath));
                children.setMeta(new MetaVo(menu.menuName(), menu.icon(), menu.path()));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

/**
 * 构建前端所需要树结构
 *
 * @param menus 菜单列表
 * @return 树结构列表
 *//*

    @Override
    public List<SysMenu> buildMenuTree(List<SysMenu> menus)
    {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());
        for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext();)
        {
            SysMenu menu = (SysMenu) iterator.next();
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (!tempList.contains(menu.getParentId()))
            {
                recursionFn(menus, menu);
                returnList.add(menu);
            }
        }
        if (returnList.isEmpty())
        {
            returnList = menus;
        }
        return returnList;
    }

    */
/**
 * 构建前端所需要下拉树结构
 *
 * @param menus 菜单列表
 * @return 下拉树结构列表
 *//*

    @Override
    public List<TreeSelect> buildMenuTreeSelect(List<SysMenu> menus)
    {
        List<SysMenu> menuTrees = buildMenuTree(menus);
        return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    */

    /**
     * 根据菜单ID查询信息
     *
     * @param menuId 菜单ID
     * @return 菜单信息
     */
    @Override
    public SysMenuView selectMenuById(Long menuId) {
        return sqlClient.findById(SysMenuView.class, menuId);
    }

    /**
     * 是否存在菜单子节点
     *
     * @param menuId 菜单ID
     * @return 结果
     */
    @Override
    public boolean hasChildByMenuId(Long menuId) {
        SysMenuTable table = SysMenuTable.$;
        return sqlClient.createQuery(table)
                .where(table.parentId().eq(menuId))
                .select(table.count())
                .fetchOne() > 0;
    }

    /**
     * 查询菜单使用数量
     *
     * @param menuId 菜单ID
     * @return 结果
     */
    @Override
    public boolean checkMenuExistRole(Long menuId) {
        SysMenuTable table = SysMenuTable.$;
        SysMenu sysMenu = sqlClient.createQuery(table)
                .where(table.menuId().eq(menuId))
                .select(table.asTableEx().fetch(SysMenuFetcher.$.roles()))
                .fetchOne();
        int result = sysMenu.roles().size();
        return result > 0;
    }


    /**
     * 新增保存菜单信息
     *
     * @param menu 菜单信息
     * @return 结果
     */
    @Override
    public int insertMenu(SysMenuSaveInput menu) {
        SysMenu sysMenu = SysMenuDraft.$
                .produce(menu.toEntity(), draft -> draft.applyCreateBy(user -> user.setUserId(SecurityUtils.getUserId())));
        return sqlClient.saveCommand(sysMenu)
                .setMode(SaveMode.INSERT_ONLY)
                .execute()
                .getTotalAffectedRowCount();
    }

    /**
     * 修改保存菜单信息
     *
     * @param menu 菜单信息
     * @return 结果
     */
    @Override
    public int updateMenu(SysMenuUpdateInput menu) {
        SysMenu sysMenu = SysMenuDraft.$
                .produce(menu.toEntity(), draft -> draft.applyUpdateBy(user -> user.setUserId(SecurityUtils.getUserId())));
        return sqlClient.saveCommand(sysMenu)
                .setMode(SaveMode.UPDATE_ONLY)
                .execute()
                .getTotalAffectedRowCount();
    }

    /**
     * 删除菜单管理信息
     *
     * @param menuId 菜单ID
     * @return 结果
     */
    @Override
    public int deleteMenuById(Long menuId) {
        SysMenuTable table = SysMenuTable.$;
        return sqlClient.createDelete(table)
                .where(table.menuId().eq(menuId))
                .execute();
    }


    /**
     * 校验菜单名称是否唯一
     *
     * @param menuId   菜单信息
     * @param menuName 菜单信息
     * @return 结果
     */
    @Override
    public boolean checkMenuNameUnique(Long menuId, String menuName, Long parentId) {
        SysMenuTable table = SysMenuTable.$;
        SysMenu info = sqlClient.createQuery(table)
                .where(table.parentId().ne(parentId), table.menuName().eq(menuName))
                .select(table.fetch(SysMenuFetcher.$.allScalarFields().parentId())).fetchOneOrNull();
        if (StringUtils.isNotNull(info) && info.menuId() != menuId) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    @Override
    public List<SysMenuTreeView> selectMenuTree(SysMenuListInput menu, Long userId) {
        List<SysMenuTreeView> menuList;
        SysMenuTable table = SysMenuTable.$;
        if (SecurityUtils.isAdmin(userId)) {
            menuList = sqlClient.createQuery(table)
                    .whereIf(StrUtil.isNotBlank(menu.getMenuName()), () -> table.menuName().ilike(menu.getMenuName()))
                    .whereIf(StrUtil.isNotBlank(menu.getStatus()), () -> table.status().eq(menu.getStatus()))
                    .where(table.parentId().eq(0L))
                    .orderBy(table.parentId(), table.orderNum())
                    .select(table.fetch(
                                    SysMenuTreeView.class
                            )
                    )
                    .execute();

        } else {
            menuList = sqlClient.createQuery(table)
                    .whereIf(StrUtil.isNotBlank(menu.getMenuName()), () -> table.menuName().ilike(menu.getMenuName()))
                    .whereIf(StrUtil.isNotBlank(menu.getStatus()), () -> table.status().eq(menu.getStatus()))
                    .where(table.asTableEx().roles().users().userId().eq(userId))
                    .where(table.parentId().eq(0L))
                    .orderBy(table.parentId(), table.orderNum())
                    .select(table.fetch(
                                    SysMenuTreeView.class
                            )
                    ).execute();
        }
        return menuList;
    }


    /**
     * 获取路由名称
     *
     * @param menu 菜单信息
     * @return 路由名称
     */

    public String getRouteName(SysMenu menu) {
        // 非外链并且是一级目录（类型为目录）
        if (isMenuFrame(menu)) {
            return StringUtils.EMPTY;
        }
        return getRouteName(menu.routeName(), menu.path());
    }

    /**
     * 获取路由名称，如没有配置路由名称则取路由地址
     *
     * @param name 路由名称
     * @param path 路由地址
     * @return 路由名称（驼峰格式）
     */
    public String getRouteName(String name, String path) {
        String routerName = StringUtils.isNotEmpty(name) ? name : path;
        return StringUtils.capitalize(routerName);
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(SysMenu menu) {
        String routerPath = menu.path();
        // 内链打开外网方式
        if (menu.parentId() != 0 && isInnerLink(menu)) {
            routerPath = innerLinkReplaceEach(routerPath);
        }
        // 非外链并且是一级目录（类型为目录）
        if (0 == menu.parentId() && UserConstants.TYPE_DIR.equals(menu.menuType())
                && UserConstants.NO_FRAME.equals(menu.frameStatus())) {
            routerPath = "/" + menu.path();
        }
        // 非外链并且是一级目录（类型为菜单）
        else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(SysMenu menu) {
        String component = UserConstants.LAYOUT;
        if (StringUtils.isNotEmpty(menu.component()) && !isMenuFrame(menu)) {
            component = menu.component();
        } else if (StringUtils.isEmpty(menu.component()) && menu.parentId() != 0 && isInnerLink(menu)) {
            component = UserConstants.INNER_LINK;
        } else if (StringUtils.isEmpty(menu.component()) && isParentView(menu)) {
            component = UserConstants.PARENT_VIEW;
        }
        return component;
    }


    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */

    public boolean isMenuFrame(SysMenu menu) {
        return menu.parentId() == 0 && UserConstants.TYPE_MENU.equals(menu.menuType())
                && menu.frameStatus().equals(UserConstants.NO_FRAME);
    }

    /**
     * 是否为内链组件
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isInnerLink(SysMenu menu) {
        return Objects.equals(menu.frameStatus(), UserConstants.NO_FRAME) && StringUtils.ishttp(menu.path());
    }

    /**
     * 是否为parent_view组件
     *
     * @param menu 菜单信息
     * @return 结果
     */

    public boolean isParentView(SysMenu menu) {
        return menu.parentId() != 0 && UserConstants.TYPE_DIR.equals(menu.menuType());
    }


    /**
     * 根据父节点的ID获取所有子节点
     *
     * @param list     分类表
     * @param parentId 传入的父节点ID
     * @return String
     */

    /*public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) {
        List<SysMenu> returnList = new ArrayList<>();
        for (SysMenu t : list) {
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.parentId() == parentId) {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }*/

/**
 * 递归列表
 *
 * @param list 分类表
 * @param t 子节点
 */

    /*private void recursionFn(List<SysMenu> list, SysMenu t)
    {
        // 得到子节点列表
        List<SysMenu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenu tChild : childList)
        {
            if (hasChild(list, tChild))
            {
                recursionFn(list, tChild);
            }
        }
    }*/

/**
 * 得到子节点列表
 *//*

    private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
    {
        List<SysMenu> tlist = new ArrayList<SysMenu>();
        Iterator<SysMenu> it = list.iterator();
        while (it.hasNext())
        {
            SysMenu n = (SysMenu) it.next();
            if (n.getParentId().longValue() == t.getMenuId().longValue())
            {
                tlist.add(n);
            }
        }
        return tlist;
    }

    */
/**
 * 判断是否有子节点
 *//*

    private boolean hasChild(List<SysMenu> list, SysMenu t)
    {
        return getChildList(list, t).size() > 0;
    }

    */

    /**
     * 内链域名特殊字符替换
     *
     * @return 替换后的内链域名
     */
    public String innerLinkReplaceEach(String path) {
        return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
                new String[]{"", "", "", "/", "/"});
    }
}

